package be.pluyckx.batteryprogress.data.history;

import android.util.Log;

import java.util.Collection;

import be.pluyckx.batteryprogress.application.Constants;
import be.pluyckx.batteryprogress.data.ObservableListImpl;
import be.pluyckx.batteryprogress.data.Observer;
import be.pluyckx.batteryprogress.utils.SortedArray;

/**
 * Created by Philip Luyckx on 11/08/13.
 */
public class BatteryHistory extends ObservableListImpl {
  private final String TAG = "BatteryHistory";

  private final SortedArray<BatteryLevel> mData;
  private int mIndexOfFirstDischarging = -1;
  private int mIndexOfLastDischarging = -1;
  private int mIndexOfFirstCharging = -1;
  private int mIndexOfLastCharging = -1;
  private int mSize = Constants.getHistorySize();
  private ObserverImpl mBatteryLevelObserver = new ObserverImpl();

  private static BatteryHistory mSingleton = new BatteryHistory();

  public static BatteryHistory getHistory() {
    return mSingleton;
  }

  private BatteryHistory() {
    mData = new SortedArray<BatteryLevel>();
  }

  public BatteryHistory(BatteryHistory history) {
    mData = new SortedArray<BatteryLevel>(history.mData);
  }

  public synchronized void add(BatteryLevel level) {
    mData.add(level);
    level.addObserver(mBatteryLevelObserver);

    if (mData.size() > mSize) {
      mData.remove(0).removeObserver(mBatteryLevelObserver);
      updateIndexes();
      fireDataChanged(this, new ChangeInfo(ChangeInfo.ACTION_REMOVE, 0, 1));
    } else {
      updateIndexes();
    }

    fireDataChanged(this, new ChangeInfo(ChangeInfo.ACTION_ADD, mData.size() - 1, mData.size() - 1));
  }

  public synchronized void setSize(int size) {
    int reduce = mData.size() - size;
    mSize = size;

    if (reduce > 0) {
      while (mData.size() > mSize) {
        mData.remove(0).removeObserver(mBatteryLevelObserver);
      }

      updateIndexes();

      if (reduce > 0) {
        fireDataChanged(this, new ChangeInfo(ChangeInfo.ACTION_REMOVE, 0, reduce - 1));
      }
    }
  }

  public synchronized void add(Collection<BatteryLevel> levels) {
    for (BatteryLevel level : levels) {
      level.addObserver(mBatteryLevelObserver);
    }
    int start = mData.size() - 1;
    mData.addAll(levels);
    int end = mData.size() - 1;

    int reduce = mData.size() - mSize;

    if (reduce > 0) {
      while (mData.size() > mSize) {
        mData.remove(0).removeObserver(mBatteryLevelObserver);
      }

      updateIndexes();

      start -= reduce;
      end -= reduce;

      fireDataChanged(this, new ChangeInfo(ChangeInfo.ACTION_REMOVE, 0, reduce - 1));
    } else {
      updateIndexes();
    }
    fireDataChanged(this, new ChangeInfo(ChangeInfo.ACTION_ADD, start, end));
  }

  public synchronized BatteryLevel get(int index) {
    return mData.get(index);
  }

  public synchronized BatteryLevel getFirstInCurrentState() {
    return (getLast().getState() == BatteryLevel.STATE_CHARGING ? getFirstCharging() : getFirstDiscarging());
  }

  public synchronized BatteryLevel getLastInCurrentState() {
    return (getLast().getState() == BatteryLevel.STATE_CHARGING ? getLastCharging() : getLastDischarging());
  }

  public synchronized void clear() {
    clear(false);
  }

  public synchronized void clear(boolean clearLast) {
    if (size() > 0) {
      BatteryLevel last = getLast();

      int size = mData.size();
      while (mData.size() > 0) {
        mData.remove(mData.size() - 1).removeObserver(mBatteryLevelObserver);
      }

      ChangeInfo info = null;
      if (!clearLast) {
        last.addObserver(mBatteryLevelObserver);
        mData.add(last);
        info = new ChangeInfo(ChangeInfo.ACTION_REMOVE, 0, size - 2);
      } else {
        info = new ChangeInfo(ChangeInfo.ACTION_REMOVE, 0, size - 1);
      }


      fireDataChanged(this, info);
    }
  }

  public synchronized int size() {
    return mData.size();
  }

  public synchronized BatteryLevel getFirstCharging() {
    if (Constants.DEBUG) {
      Log.d(TAG, "first charging: " + mIndexOfFirstCharging);
    }

    if (mIndexOfFirstCharging < 0) {
      return null;
    } else {
      return mData.get(mIndexOfFirstCharging);
    }
  }

  public synchronized BatteryLevel getFirstDiscarging() {
    if (Constants.DEBUG) {
      Log.d(TAG, "first dis: " + mIndexOfFirstDischarging);
    }

    if (mIndexOfFirstDischarging < 0) {
      return null;
    } else {
      return mData.get(mIndexOfFirstDischarging);
    }
  }

  public synchronized BatteryLevel getLastCharging() {
    if (Constants.DEBUG) {
      Log.d(TAG, "last charging: " + mIndexOfLastCharging);
    }

    if (mIndexOfLastCharging < 0) {
      return null;
    } else {
      return mData.get(mIndexOfLastCharging);
    }
  }

  public synchronized BatteryLevel getLastDischarging() {
    if (Constants.DEBUG) {
      Log.d(TAG, "last dis: " + mIndexOfLastDischarging);
    }

    if (mIndexOfLastDischarging < 0) {
      return null;
    } else {
      return mData.get(mIndexOfLastDischarging);
    }
  }

  public synchronized BatteryLevel getFirst() {
    return mData.get(0);
  }

  public synchronized BatteryLevel getLast() {
    return mData.get(mData.size() - 1);
  }

  public synchronized BatteryLevel getCurrent() {
    return getLast();
  }

  @Override
  public boolean addObserver(Observer observer) {
    boolean ret = super.addObserver(observer);

    fireDataChanged(this, new ChangeInfo(ChangeInfo.ACTION_INIT, 0, mData.size() - 1));

    return ret;
  }

  public void setBatteryLevels(Collection<BatteryLevel> levels) {
    int size = mData.size();
    mData.clear();
    mIndexOfFirstCharging = mIndexOfLastCharging = mIndexOfFirstDischarging = mIndexOfLastDischarging = -1;
    fireDataChanged(this, new ChangeInfo(ChangeInfo.ACTION_REMOVE, 0, size - 1));
    add(levels);
  }

  private void updateIndexes() {
    mIndexOfLastCharging = mData.size() - 1;
    mIndexOfLastDischarging = mData.size() - 1;
    if (mData.size() == 0) {
      mIndexOfFirstCharging = mIndexOfFirstDischarging = -1;
      return;
    }

    BatteryLevel last = getLast();

    if (last.getState() == BatteryLevel.STATE_CHARGING) {
      mIndexOfFirstCharging = mIndexOfLastCharging - 1;
      while (mIndexOfFirstCharging >= 0 && mData.get(mIndexOfFirstCharging).getState() == BatteryLevel.STATE_CHARGING) {
        mIndexOfFirstCharging--;
      }

      mIndexOfFirstCharging++;

      mIndexOfLastDischarging = mIndexOfFirstCharging - 1;
      mIndexOfFirstDischarging = mIndexOfLastDischarging - 1;
      while (mIndexOfFirstDischarging >= 0 && mData.get(mIndexOfFirstDischarging).getState() == BatteryLevel.STATE_DISCHARGING) {
        mIndexOfFirstDischarging--;
      }

      mIndexOfFirstDischarging++;
    } else {
      mIndexOfFirstDischarging = mIndexOfLastDischarging - 1;
      while (mIndexOfFirstDischarging >= 0 && mData.get(mIndexOfFirstDischarging).getState() == BatteryLevel.STATE_DISCHARGING) {
        mIndexOfFirstDischarging--;
      }

      mIndexOfFirstDischarging++;

      mIndexOfLastCharging = mIndexOfFirstDischarging - 1;
      mIndexOfFirstCharging = mIndexOfLastCharging - 1;
      while (mIndexOfFirstCharging >= 0 && mData.get(mIndexOfFirstCharging).getState() == BatteryLevel.STATE_CHARGING) {
        mIndexOfFirstCharging--;
      }

      mIndexOfFirstCharging++;
    }
  }

  private class ObserverImpl implements Observer {

    @Override
    public void onDataChanged(Object observable, ChangeInfo info) {
      int index = mData.indexOf((BatteryLevel) observable);
      fireDataChanged(BatteryHistory.this, new BatteryHistory.ChangeInfo(BatteryHistory.ChangeInfo.ACTION_UPDATE, index, index));
    }
  }

  public class ChangeInfo implements Observer.ChangeInfo {
    public static final int ACTION_ADD = 1;
    public static final int ACTION_UPDATE = 2;
    public static final int ACTION_REMOVE = 3;
    public static final int ACTION_INIT = 4;

    public final int action;

    public final int start;
    public final int end;

    public ChangeInfo(int action) {
      this(action, -1, -1);
    }

    public ChangeInfo(int action, int start, int end) {
      this.action = action;
      this.start = start;
      this.end = end;
    }
  }
}
